package magnet.processor.instances;

import magnet.Instance;
import magnet.processor.MagnetProcessorEnv;
import magnet.processor.instances.aspects.index.FactoryIndexCodeGenerator;
import magnet.processor.instances.generator.CodeWriter;
import magnet.processor.instances.generator.FactoryTypeCodeGenerator;
import magnet.processor.instances.parser.InstanceParserForClass;
import magnet.processor.instances.parser.InstanceParserForMethod;

import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.ElementFilter;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

public class InstanceProcessor {

    private MagnetProcessorEnv env;

    private InstanceParserForClass factoryFromClassAnnotationParser;
    private InstanceParserForMethod factoryFromMethodAnnotationParser;
    private FactoryTypeCodeGenerator factoryTypeCodeGenerator;
    private FactoryIndexCodeGenerator factoryIndexCodeGenerator;

    public InstanceProcessor(MagnetProcessorEnv env) {
        this.env = env;
        factoryFromClassAnnotationParser = new InstanceParserForClass(env);
        factoryFromMethodAnnotationParser = new InstanceParserForMethod(env);
        factoryTypeCodeGenerator = new FactoryTypeCodeGenerator();
        factoryIndexCodeGenerator = new FactoryIndexCodeGenerator();
    }

    public boolean process(RoundEnvironment roundEnv) throws Throwable {

        Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(Instance.class);
        if (annotatedElements.isEmpty()) {
            return false;
        }

        ArrayList<FactoryType> factoryTypes = new ArrayList<>();
        Set<TypeElement> typeElements = ElementFilter.typesIn(annotatedElements);
        for (TypeElement element : typeElements) {
            List<FactoryType> parsedFactoryTypes = factoryFromClassAnnotationParser.parse(element);
            for (FactoryType factoryType : parsedFactoryTypes) {
                if (!factoryType.isDisabled()) {
                    factoryTypes.add(factoryType);
                }
            }
        }

        Set<ExecutableElement> executableElements = ElementFilter.methodsIn(annotatedElements);
        for (ExecutableElement element : executableElements) {
            List<FactoryType> parsedFactoryTypes = factoryFromMethodAnnotationParser.parse(element);
            for (FactoryType factoryType : parsedFactoryTypes) {
                if (!factoryType.isDisabled()) {
                    factoryTypes.add(factoryType);
                }
            }
        }

        factoryTypes.sort(new Comparator<FactoryType>() {
            @Override
            public int compare(FactoryType o1, FactoryType o2) {
                return o1.getFactoryType().simpleName().compareTo(o2.getFactoryType().simpleName());
            }
        });

        ArrayList<CodeWriter> codeWriters = new ArrayList<>();
        for (FactoryType factoryType : factoryTypes) {
            codeWriters.add(factoryTypeCodeGenerator.generateFrom(factoryType));
            codeWriters.add(factoryIndexCodeGenerator.generateFrom(factoryType));
        }

        for (CodeWriter codeWriter : codeWriters) {
            codeWriter.writeInto(env.getFiler());
        }

        return true;
    }

}


